package com.pingan.haofang.searchcloud.quartz.service;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.pingan.haofang.searchcloud.common.exception.SystemException;
import com.pingan.haofang.searchcloud.quartz.constants.QuartzConstants;
import com.pingan.haofang.searchcloud.quartz.dto.ArgsDto;
import com.pingan.haofang.searchcloud.quartz.dto.ScheduleTask;
import org.apache.commons.lang3.StringUtils;
import org.quartz.CronScheduleBuilder;
import org.quartz.CronTrigger;
import org.quartz.JobDataMap;
import org.quartz.JobDetail;
import org.quartz.JobKey;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.TriggerBuilder;
import org.quartz.TriggerKey;
import org.quartz.impl.matchers.GroupMatcher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jmx.export.annotation.ManagedAttribute;
import org.springframework.jmx.export.annotation.ManagedOperation;
import org.springframework.jmx.export.annotation.ManagedResource;
import org.springframework.scheduling.quartz.JobDetailFactoryBean;
import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

import static com.pingan.haofang.searchcloud.index.constants.IndexBuildConstants.ARGS;
import static com.pingan.haofang.searchcloud.index.constants.IndexBuildConstants.IS_MANUAL_JOB;
import static com.pingan.haofang.searchcloud.index.constants.IndexBuildConstants.IS_RESUME_JOB;
import static com.pingan.haofang.searchcloud.index.constants.IndexBuildConstants.MANUAL_JOB;
import static com.pingan.haofang.searchcloud.index.constants.IndexBuildConstants.RESUME_JOB;

/**
 * 定时任务管理器
 *
 * @author LUYI374
 * @date 2017年5月8日
 * @since 1.0.0
 */
@Component
@ManagedResource
public class ScheduleTaskManager {

    static Logger LOG = LoggerFactory.getLogger(ScheduleTaskManager.class);

    private static Gson GSON = new GsonBuilder().setDateFormat("yyyy-MM-dd HH:mm:ss").create();

    @Autowired
    private Scheduler scheduler;

    /**
     * 注册定时任务
     *
     * @param task
     */

    public void register(ScheduleTask task) {
        try {

            TriggerKey triggerKey = TriggerKey.triggerKey(task.getName(), task.getGroup());
            CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);

            //JobDetailFactoryBean没有提供让quartz作业串行执行的配置
            JobDetailFactoryBean jobFactory = buildScheduleTaskInvokeFactory(task);

            JobDetail jobDetail = jobFactory.getObject();

            //如果上次任务被中断则继续执行
            jobDetail = jobDetail.getJobBuilder().requestRecovery().build();

            // 表达式调度构建器
            CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(task.getCron());
            if (QuartzConstants.INC_METHOD.equals(task.getMethod())) {
                scheduleBuilder.withMisfireHandlingInstructionDoNothing();
            } else if (QuartzConstants.FULL_METHOD.equals(task.getMethod())) {
                scheduleBuilder.withMisfireHandlingInstructionFireAndProceed();
            }

            // 不存在，创建一个
            if (null == trigger) {
                LOG.info("register new schedule task, group:{}, name:{}", task.getGroup(), task.getName());
                // 按新的cronExpression表达式构建一个新的trigger
                trigger = TriggerBuilder.newTrigger().withIdentity(task.getName(), task.getGroup())
                        .withSchedule(scheduleBuilder).startNow().build();
                scheduler.scheduleJob(jobDetail, trigger);
            } else {
                // Trigger已存在，那么更新相应的定时设置
                // 表达式调度构建器
                // 按新的cronExpression表达式重新构建trigger
                trigger = trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder).startNow().build();
                // 按新的trigger重新设置job执行

                scheduler.addJob(jobDetail, true);
                scheduler.rescheduleJob(triggerKey, trigger);

            }
        } catch (SchedulerException e) {
            LOG.error("register schedule task failed, group:{}, name:{}", task.getGroup(), task.getName(), e);
            throw new SystemException(e);
        }
    }

    private JobDetailFactoryBean buildScheduleTaskInvokeFactory(ScheduleTask task) {
        JobDetailFactoryBean jobFactory = new JobDetailFactoryBean();
        jobFactory.setGroup(task.getGroup());
        jobFactory.setName(task.getName());
        jobFactory.setDurability(true);
        jobFactory.setJobClass(SchduleQuartzJobBean.class);
        String argsStr = GSON.toJson(task, ScheduleTask.class);
        jobFactory.getJobDataMap().put(ARGS, argsStr);
        jobFactory.afterPropertiesSet();
        return jobFactory;
    }

    /**
     * 手动触发触发任务执行
     *
     * @param group
     * @param name
     */
    public void run(String group, String name, ArgsDto argsDto) {

        try {
            JobKey jobKey = new JobKey(name, group);
            JobDetailFactoryBean jobFactory = buildJobDetailFactoryBeanBeforeForManual(jobKey, argsDto);

            JobDataMap jobDataMap = jobFactory.getJobDataMap();

            //在jobDataMap中添加MANUAL_JOB，仅作参数传递，获取参数后删除
            jobDataMap.put(MANUAL_JOB, IS_MANUAL_JOB);

            scheduler.triggerJob(jobKey, jobDataMap);
        } catch (SchedulerException e) {
            LOG.error("execute schedule task failed, group:{}, name:{}", group, name, e);
            throw new SystemException(e);
        }
    }

    /**
     * 恢复任务
     *
     * @param group
     * @param name
     */
    public void resume(String group, String name) {

        try {
            JobKey jobKey = new JobKey(name, group);
            JobDetailFactoryBean jobFactory = buildJobDetailFactoryBeanBeforeForManual(jobKey, null);
            JobDataMap jobDataMap = jobFactory.getJobDataMap();

            //在jobDataMap中添加RESUME_JOB，仅作参数传递，获取参数后删除
            jobDataMap.put(RESUME_JOB, IS_RESUME_JOB);

            scheduler.triggerJob(jobKey, jobDataMap);
        } catch (SchedulerException e) {
            LOG.error("execute schedule task failed, group:{}, name:{}", group, name, e);
            throw new SystemException(e);
        }
    }


    public void pause(String group, String name) throws SchedulerException {
        Objects.requireNonNull(group, "group can not be null!");
        Objects.requireNonNull(name, "name can not be null!");
        this.pause(new JobKey(name, group));
    }

    public void pause(JobKey jobKey) throws SchedulerException {
        Objects.requireNonNull(jobKey, "jobKey can not be null!");
        if (scheduler.checkExists(jobKey)) {
            LOG.info("invalid schedule group:{} and name:{}", jobKey.getGroup(), jobKey.getName());
            return;
        }
        scheduler.pauseJob(jobKey);
    }

    private JobDetailFactoryBean buildJobDetailFactoryBeanBeforeForManual(JobKey jobKey, ArgsDto argsDto) throws
            SchedulerException {
        JobDetail jobDetail = scheduler.getJobDetail(jobKey);
        if (jobDetail == null) {
            throw new SystemException("cannot found available schedule task, group: " + jobKey.getGroup() + ", name:"
                    + jobKey.getName());
        }
        ScheduleTask oldTask = getScheduleTaskFromJobtail(jobDetail);

        ScheduleTask newTask = new ScheduleTask();
        newTask.setGroup(oldTask.getGroup());
        newTask.setName(oldTask.getName());

        newTask.setCron(oldTask.getCron());
        newTask.setDesc(oldTask.getDesc());
        newTask.setMethod(oldTask.getMethod());
        newTask.setTarget(oldTask.getTarget());

        if (argsDto != null) {
            newTask.setArgs(argsDto);
        } else {
            newTask.setArgs(oldTask.getArgs());
        }
        return buildScheduleTaskInvokeFactory(newTask);
    }

    /**
     * 从jobdetail获得task
     *
     * @param jobDetail
     * @return
     */
    private ScheduleTask getScheduleTaskFromJobtail(JobDetail jobDetail) {
        String str = (String) jobDetail.getJobDataMap().get(ARGS);
        String argsStr = StringUtils.substring(str, StringUtils.indexOf(str, "{"));
        return GSON.fromJson(argsStr, ScheduleTask.class);
    }

    /**
     * 删除定时任务
     *
     * @param group
     * @param name
     */
    public void delete(String group, String name) {
        try {
            TriggerKey key = TriggerKey.triggerKey(name, group);
            scheduler.unscheduleJob(key);
            scheduler.deleteJob(new JobKey(name, group));
            LOG.info("delete schedule task, group:{}, name:{}", group, name);
        } catch (SchedulerException e) {
            LOG.error("delete schedule task failed, group:{}, name:{}", group, name, e);
            throw new SystemException(e);
        }
    }

    /**
     * 获取单个任务
     *
     * @param group
     * @param name
     * @return
     * @throws SchedulerException
     */
    public JobDetail get(String group, String name) throws SchedulerException {
        return scheduler.getJobDetail(JobKey.jobKey(name, group));
    }

    /**
     * 获取所有任务
     *
     * @return
     * @throws SchedulerException
     */
    @ManagedOperation
    public Map<TriggerKey, ScheduleTask> getAll() throws SchedulerException {
        Map<TriggerKey, ScheduleTask> map = new HashMap<>();
        GroupMatcher<JobKey> matcher = GroupMatcher.anyJobGroup();
        Set<JobKey> jobKeys = scheduler.getJobKeys(matcher);
        for (JobKey jobKey : jobKeys) {
            JobDetail jobDetail = scheduler.getJobDetail(jobKey);
            ScheduleTask task = getScheduleTaskFromJobtail(jobDetail);
            map.put(TriggerKey.triggerKey(jobKey.getName(), jobKey.getGroup()), task);
        }
        return map;
    }

    @ManagedOperation
    public Map<TriggerKey, ScheduleTask> getAllFull() throws SchedulerException {
        Map<TriggerKey, ScheduleTask> map = new HashMap<>();
        GroupMatcher<JobKey> incMatcher = GroupMatcher.groupEndsWith(QuartzConstants.INC_SCHEDULE_GROUP_SUFFIX);
        GroupMatcher<JobKey> anyMatcher = GroupMatcher.anyJobGroup();
        Set<JobKey> incJobKeys = scheduler.getJobKeys(incMatcher);
        Set<JobKey> allJobKeys = scheduler.getJobKeys(anyMatcher);
        allJobKeys.removeAll(incJobKeys);
        for (JobKey jobKey : allJobKeys) {
            JobDetail jobDetail = scheduler.getJobDetail(jobKey);
            ScheduleTask task = getScheduleTaskFromJobtail(jobDetail);
            map.put(TriggerKey.triggerKey(jobKey.getName(), jobKey.getGroup()), task);
        }
        return map;
    }


    @ManagedOperation
    public Map<TriggerKey, ScheduleTask> getAllInc() throws SchedulerException {
        Map<TriggerKey, ScheduleTask> map = new HashMap<>();
        GroupMatcher<JobKey> matcher = GroupMatcher.groupEndsWith(QuartzConstants.INC_SCHEDULE_GROUP_SUFFIX);
        Set<JobKey> jobKeys = scheduler.getJobKeys(matcher);
        for (JobKey jobKey : jobKeys) {
            JobDetail jobDetail = scheduler.getJobDetail(jobKey);
            ScheduleTask task = getScheduleTaskFromJobtail(jobDetail);
            map.put(TriggerKey.triggerKey(jobKey.getName(), jobKey.getGroup()), task);
        }
        return map;
    }

    /**
     * jmx 监控
     **/
    @ManagedAttribute
    public Scheduler getScheduler() {
        return scheduler;
    }


}